#include <cstdio>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <sstream>

#include "NetSock.h"
#include "packets.h"
#include "cConnection.h"
#include "cDatabase.h"
#include "cCrypto.h"

#include "macros.h"

#define DEFAULT_PORT 8100
#define DEFAULT_DB_FILE "ijrs.db"

int last_exit = 0;
std::vector<cConnection*> connections;

int port = DEFAULT_PORT;

unsigned int admin_reserved_connections = 1;
unsigned int max_connections = 3;
NetSock* sock;

//	ID	|	  CLIENT=>SV	|	  SERVER=>CL
//  0   |     NOT USED      |      MESSAGE
//	1		LOGIN REQUEST		LOGIN RESPONSE
//  2	  USER DATA REQUEST    USER DATA UPDATE
//

const char* response_server_full = "\x01\x01";

void TidyConnectionQueue();

void OnExit() {
	LOG(" == Quitting... ==\n");
	LOG("Closing database...\n");
	cDatabase::Get()->Close();
	LOG("Deleting listen socket...\n");
	delete sock;
	LOG("Disconnecting all clients...\n");
	TidyConnectionQueue();
	for( size_t i = 0; i < connections.size(); i++ ) {
		cConnection* conn = connections[i];
		if(conn->Closed) continue;
		conn->ClearQueue();
		conn->QueuePacket(new PACKET_MESSAGE("Server is shutting down. Please try to connect again later"));
		conn->QueuePacket(new PACKET_TERMINATOR());
	}
	std::this_thread::sleep_for( std::chrono::seconds(1) );
	LOG("Server shut down.\n");
	if(last_exit != 0) {
		LOG("Server exited with error code %d. Press enter to exit application...\n", last_exit);
		getchar();
	}
}


void TidyConnectionQueue() {
	std::vector< cConnection* > newConnections;
	for(size_t i = 0; i < connections.size(); i++)
		if( !connections[i]->Closed ) 
			newConnections.push_back(connections[i]); 
		else 
			delete connections[i];
	connections = newConnections;
}


int main(int argc, char** argv) {
	atexit( OnExit );

	LOG(" == Starting iJRSd... ==\n");

	const char* db_filename = DEFAULT_DB_FILE;

	if(argc > 1) {
		for( int i = 1; i < argc; i++ ) {
			std::string currParam (argv[i]);
			if(!currParam.compare("--help")) {
				fprintf(stderr, "  USAGE: ijrsd [parameters]\n\n"
								"  Parameters available:\n"
								"--port <port number>        Binds to given port. Default: " _STR(DEFAULT_PORT) "\n"
								"--dbfile <filename>         Uses specified file as databse. Default: " DEFAULT_DB_FILE "\n"
								"--help                      Shows this help message and exits the program.\n\n" );
				exit(0);
			}else if(!currParam.compare("--port")) {
				if(++i==argc) {
					ERRORLOG("error: missing port number after '--port'!\n");
					exit(1);
				}
				port = atoi(argv[i]);
				if( port > 65535 || port <= 0 ) {
					ERRORLOG("error: port out of range! (range: 1-65535, got %d)\n", port);
					exit(1);
				}
			}else if(!currParam.compare("--dbfile")) {
				if(++i==argc) {
					ERRORLOG("error: missing filename after '--dbname'!\n");
					exit(1);
				}
				db_filename = argv[i];
			}else{
				ERRORLOG("error: unknown parameter: %s\n", argv[i]);
				exit(1);
			}
		}
	} // finished parsing command line options
	
	// Database initialization

	LOG("Initialising database (%s)...\n", db_filename);
	if(!cDatabase::Init(db_filename)) {
		ERRORLOG("Couldn't initialise database!\n");
		exit(2);
	}
	LOG("Database ready.\n");

	

	DBResult res = cDatabase::Get()->Query("select * from users");
	for( int i = 0; i < res.GetRows(); i++ ) {
		std::string line ("");
		for( int j = 0; j < res.GetColumns(); j++) {
			line.append(res.data[i][j]);
			line.append(" ");
			for( int k = res.data[i][j].length(); k < 16; k++ ) line.append(" ");
		}
		printf("Row %d: %s\n", i, line.c_str());
	}



	// Network initialization

	LOG("Initialising networking...\n");
	if(!NetSock::InitNetworking()) {
		ERRORLOG("error: couldn't initialize netwoking\n", port);
		exit(2);
	}
	NetSock* listen_socket = new NetSock();
	if(!listen_socket->ListenAll( (ushort)port )) {
		ERRORLOG("error: couldn't open port %d for listening\n", port);
		exit(2);
	}
	LOG( "Server started! Listening on port %d.\n\n", port );
	
	

	// Accept connections
	while( (sock = listen_socket->Accept()) != 0) {
		log_net_debug2("Got Connection!\n");
		cConnection* conn = new cConnection(sock);
		log_net_debug("New connection: %s\n", conn->GetSocketString().c_str());

		TidyConnectionQueue();

		if( connections.size() >= max_connections ) {
			LOG("Server full, disconnecting %s.\n", conn->GetSocketString().c_str() );
			conn->QueuePacket(new PACKET_MESSAGE("Server is full, please try again later"));
			conn->QueuePacket(new PACKET_TERMINATOR());
			continue;
		}
		connections.push_back(conn);
		//conn->QueuePacket(new PACKET_MESSAGE("Test message."));
		//conn->QueuePacket(new PACKET_TERMINATOR());
		
	}
	

	return 0;
}

